/*=============================================================================
*
*   Copyright (C) 2020 All rights reserved.
*
*   Filename:		record.hpp
*
*   Author: Wang Zhecheng - wangzhecheng@yeah.net
*
*   Date: 2020-03-01 17:58
*
*   Last Editors: Wang Zhecheng - wangzhecheng@yeah.net
*
*   Last modified:	2020-08-04 01:24
*
*   Description:
*
=============================================================================*/
#ifndef RECORD_H
#define RECORD_H

#include "../arithmetic/tensor.hpp"
#include "regex.hpp"
#include <omp.h>

/*operators on std::unordered_map<std::string,T>{{{*/
template <typename T>
std::unordered_map<std::string, T>
operator+(const std::unordered_map<std::string, T>& lhs,
          const std::unordered_map<std::string, T>& rhs) {
  std::unordered_map<std::string, T> ans;
  for(auto& rec : lhs) {
    auto key = rec.first;
    if(rhs.find(key) == rhs.end())
      throw "records inconsistent";
    T v1     = rec.second;
    T v2     = rhs.at(key);
    ans[key] = v1 + v2;
  }
  return ans;
}
template <typename T>
std::unordered_map<std::string, T>
operator-(const std::unordered_map<std::string, T>& lhs,
          const std::unordered_map<std::string, T>& rhs) {
  std::unordered_map<std::string, T> ans;
  for(auto& rec : lhs) {
    auto key = rec.first;
    if(rhs.find(key) == rhs.end())
      throw "records inconsistent";
    T v1     = rec.second;
    T v2     = rhs.at(key);
    ans[key] = v1 - v2;
  }
  return ans;
}
template <typename T, typename P>
std::unordered_map<std::string, T>
operator*(const std::unordered_map<std::string, T>& lhs, const P& rhs) {
  std::unordered_map<std::string, T> ans;
  for(auto& rec : lhs) {
    ans[rec.first] = rec.second * rhs;
  }
  return ans;
}
template <typename T, typename P>
std::unordered_map<std::string, T>
operator/(const std::unordered_map<std::string, T>& lhs, const P& rhs) {
  std::unordered_map<std::string, T> ans;
  for(auto& rec : lhs) {
    ans[rec.first] = rec.second / rhs;
  }
  return ans;
}

template <typename T>
std::unordered_map<std::string, T>&
operator+=(std::unordered_map<std::string, T>&       lhs,
           const std::unordered_map<std::string, T>& rhs) {
  for(auto& rec : lhs) {
    auto key = rec.first;
    if(rhs.find(key) == rhs.end())
      throw "records inconsistent";
    lhs[key] += rhs.at(key);
  }
  return lhs;
}
template <typename T>
std::unordered_map<std::string, T>&
operator-=(std::unordered_map<std::string, T>&       lhs,
           const std::unordered_map<std::string, T>& rhs) {
  for(auto& rec : lhs) {
    auto key = rec.first;
    if(rhs.find(key) == rhs.end())
      throw "records inconsistent";
    lhs[key] -= rhs.at(key);
  }
  return lhs;
}
template <typename T, typename P>
std::unordered_map<std::string, T>
operator*=(std::unordered_map<std::string, T>& lhs, const P& rhs) {
  for(auto& rec : lhs) {
    lhs[rec.first] = rec.second * rhs;
  }
  return lhs;
}
template <typename T, typename P>
std::unordered_map<std::string, T>
operator/=(std::unordered_map<std::string, T>& lhs, const P& rhs) {
  for(auto& rec : lhs) {
    lhs[rec.first] = rec.second / rhs;
  }
  return lhs;
}

template <typename T>
std::ostream& operator<<(std::ostream&                             os,
                         const std::unordered_map<std::string, T>& rhs) {
  for(auto& rec : rhs)
    os << "\t" << rec.first << " : " << rec.second << "\n";
  return os;
}
/*}}}*/

namespace geoxfem {
namespace util {

template <typename DimensionType, typename CasePolicy>
struct basic_record {
  using Strain = arithmetic::Strain<DimensionType>;
  using Stress = arithmetic::Stress<DimensionType>;

  basic_record()                    = default;
  basic_record(const basic_record&) = default;
  basic_record(basic_record&&)      = default;
  basic_record& operator=(const basic_record&) = default;
  basic_record& operator=(basic_record&&) = default;
  ~basic_record()                         = default;

  void reset() { /*{{{*/
    for(auto& v : m_scalar_list)
      v.second = 0;
    for(auto& v : m_vector_list)
      v.second.setZero();
    for(auto& v : m_stress_list)
      v.second.setZero();
    for(auto& v : m_strain_list)
      v.second.setZero();
    return;
  } /*}}}*/

  /*member access{{{*/
  template <typename T>
  void addField(const std::string&, const T& = T{}) {
    GEOXFEM_NOT_IMPLEMENTED();
  }
  template <>
  void addField<_real>(const std::string& raw_key, const _real& value) {
    std::string key    = CasePolicy::apply(raw_key);
    m_scalar_list[key] = value;
  }
  template <>
  void addField<VectorXR>(const std::string& raw_key, const VectorXR& value) {
    std::string key    = CasePolicy::apply(raw_key);
    m_vector_list[key] = value;
  }
  template <>
  void addField<Stress>(const std::string& raw_key, const Stress& value) {
    std::string key    = CasePolicy::apply(raw_key);
    m_stress_list[key] = value;
  }
  template <>
  void addField<Strain>(const std::string& raw_key, const Strain& value) {
    std::string key    = CasePolicy::apply(raw_key);
    m_strain_list[key] = value;
  }

  template <typename T>
  T& getValue(const std::string&) {
    throw "not implemented yet";
  }
  template <>
  _real& getValue<_real>(const std::string& raw_key) {
    std::string key = CasePolicy::apply(raw_key);
    if(m_scalar_list.find(key) == m_scalar_list.end())
      throw "no key " + key + " in scalar list";
    return m_scalar_list.at(key);
  }
  template <>
  VectorXR& getValue<VectorXR>(const std::string& raw_key) {
    std::string key = CasePolicy::apply(raw_key);
    if(m_vector_list.find(key) == m_vector_list.end())
      throw "no key " + key + " in scalar list";
    return m_vector_list.at(key);
  }
  template <>
  Stress& getValue<Stress>(const std::string& raw_key) {
    std::string key = CasePolicy::apply(raw_key);
    if(m_stress_list.find(key) == m_stress_list.end())
      throw "no key " + key + " in scalar list";
    return m_stress_list.at(key);
  }
  template <>
  Strain& getValue<Strain>(const std::string& raw_key) {
    std::string key = CasePolicy::apply(raw_key);
    if(m_strain_list.find(key) == m_strain_list.end())
      throw "no key " + key + " in scalar list";
    return m_strain_list.at(key);
  }

  template <typename T>
  T getValue(const std::string&) const {
    throw "not implemented yet";
  }
  template <>
  _real getValue<_real>(const std::string& raw_key) const {
    std::string key = CasePolicy::apply(raw_key);
    if(m_scalar_list.find(key) == m_scalar_list.end())
      throw "no key " + key + " in scalar list";
    return m_scalar_list.at(key);
  }
  template <>
  VectorXR getValue<VectorXR>(const std::string& raw_key) const {
    std::string key = CasePolicy::apply(raw_key);
    if(m_vector_list.find(key) == m_vector_list.end())
      throw "no key " + key + " in scalar list";
    return m_vector_list.at(key);
  }
  template <>
  Stress getValue<Stress>(const std::string& raw_key) const {
    std::string key = CasePolicy::apply(raw_key);
    if(m_stress_list.find(key) == m_stress_list.end())
      throw "no key " + key + " in scalar list";
    return m_stress_list.at(key);
  }
  template <>
  Strain getValue<Strain>(const std::string& raw_key) const {
    std::string key = CasePolicy::apply(raw_key);
    if(m_strain_list.find(key) == m_strain_list.end())
      throw "no key " + key + " in scalar list";
    return m_strain_list.at(key);
  }
  /*}}}*/
  /*arithmetic{{{*/

  basic_record operator+(const basic_record& rhs) const {
    basic_record ans{};
    ans.m_scalar_list = m_scalar_list + rhs.m_scalar_list;
    ans.m_vector_list = m_vector_list + rhs.m_vector_list;
    ans.m_stress_list = m_stress_list + rhs.m_stress_list;
    ans.m_strain_list = m_strain_list + rhs.m_strain_list;
    return ans;
  }
  basic_record operator-(const basic_record& rhs) const {
    basic_record ans{};
    ans.m_scalar_list = m_scalar_list - rhs.m_scalar_list;
    ans.m_vector_list = m_vector_list - rhs.m_vector_list;
    ans.m_stress_list = m_stress_list - rhs.m_stress_list;
    ans.m_strain_list = m_strain_list - rhs.m_strain_list;
    return ans;
  }
  template <typename T>
  basic_record operator*(const T& rhs) const {
    basic_record ans{};
    ans.m_scalar_list = m_scalar_list * rhs;
    ans.m_vector_list = m_vector_list * rhs;
    ans.m_stress_list = m_stress_list * rhs;
    ans.m_strain_list = m_strain_list * rhs;
    return ans;
  }
  template <typename T>
  basic_record operator/(const T& rhs) const {
    basic_record ans{};
    ans.m_scalar_list = m_scalar_list / rhs;
    ans.m_vector_list = m_vector_list / rhs;
    ans.m_stress_list = m_stress_list / rhs;
    ans.m_strain_list = m_strain_list / rhs;
    return ans;
  }
  basic_record& operator+=(const basic_record& rhs) {
    m_scalar_list += rhs.m_scalar_list;
    m_vector_list += rhs.m_vector_list;
    m_stress_list += rhs.m_stress_list;
    m_strain_list += rhs.m_strain_list;
    return *this;
  }
  basic_record& operator-=(const basic_record& rhs) {
    m_scalar_list -= rhs.m_scalar_list;
    m_vector_list -= rhs.m_vector_list;
    m_stress_list -= rhs.m_stress_list;
    m_strain_list -= rhs.m_strain_list;
    return *this;
  }
  template <typename T>
  basic_record& operator*=(const T& rhs) {
    m_scalar_list *= rhs;
    m_vector_list *= rhs;
    m_stress_list *= rhs;
    m_strain_list *= rhs;
    return *this;
  }
  template <typename T>
  basic_record& operator/=(const T& rhs) {
    m_scalar_list /= rhs;
    m_vector_list /= rhs;
    m_stress_list /= rhs;
    m_strain_list /= rhs;
    return *this;
  }
  /*}}}*/

  friend std::ostream& operator<<(std::ostream& os, const basic_record& shp) {
    if(!shp.m_scalar_list.empty())
      os << "scalar list:\n" << shp.m_scalar_list;
    if(!shp.m_vector_list.empty())
      os << "vector list:\n" << shp.m_vector_list;
    if(!shp.m_stress_list.empty())
      os << "stress list:\n" << shp.m_stress_list;
    if(!shp.m_strain_list.empty())
      os << "strain list:\n" << shp.m_strain_list;
    return os;
  }

private:
  std::unordered_map<std::string, _real>    m_scalar_list;
  std::unordered_map<std::string, VectorXR> m_vector_list;
  std::unordered_map<std::string, Stress>   m_stress_list;
  std::unordered_map<std::string, Strain>   m_strain_list;
};  // namespace util

template <typename DimensionType>
using Record = basic_record<DimensionType, case_sensitive>;

template <typename DimensionType>
using iRecord = basic_record<DimensionType, ignore_case>;

}  // namespace util
}  // namespace geoxfem
#endif /* RECORD_H */
